/*
 * Dongle BUS interface for USB, OS independent
 *
 * Copyright (C) 1999-2016, Broadcom Corporation
 *
 *      Unless you and Broadcom execute a separate written software license
 * agreement governing use of this software, this software is licensed to you
 * under the terms of the GNU General Public License version 2 (the "GPL"),
 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
 * following added to such license:
 *
 *      As a special exception, the copyright holders of this software give you
 * permission to link this software with independent modules, and to copy and
 * distribute the resulting executable under terms of your choice, provided that
 * you also meet, for each linked independent module, the terms and conditions
 * of the license of that module.  An independent module is a module which is
 * not derived from this software.  The special exception does not apply to any
 * modifications of the software.
 *
 *      Notwithstanding the above, under no circumstances may you combine this
 * software in any way with any other Broadcom software provided under a license
 * other than the GPL, without Broadcom's express prior written consent.
 *
 *
 * <<Broadcom-WL-IPTag/Open:>>
 *
 * $Id: dbus_usb.c 565557 2015-06-22 19:29:44Z $
 */

/**
 * @file @brief
 * This file contains DBUS code that is USB, but not OS specific. DBUS is a
 * Broadcom proprietary host specific abstraction layer.
 */

#include <osl.h>
#include <bcmdefs.h>
#include <bcmutils.h>
#include <dbus.h>
#include <usbrdl.h>
#include <bcmdevs.h>
#include <bcmendian.h>

uint dbus_msglevel = DBUS_ERROR_VAL;
module_param(dbus_msglevel, int, 0);

#define USB_DLIMAGE_RETRY_TIMEOUT 3000  /* retry Timeout */
#define USB_SFLASH_DLIMAGE_SPINWAIT 150 /* in unit of ms */
#define USB_SFLASH_DLIMAGE_LIMIT 2000   /* spinwait limit (ms) */
#define POSTBOOT_ID 0xA123              /* ID to detect if dongle has boot up */
#define USB_RESETCFG_SPINWAIT 1         /* wait after resetcfg (ms) */
#define USB_DEV_ISBAD(u) (u->pub->attrib.devid == 0xDEAD)
#define USB_DLGO_SPINWAIT 100 /* wait after DL_GO (ms) */
#define TEST_CHIP 0x4328

typedef struct {
    dbus_pub_t *pub;

    void *cbarg;
    dbus_intf_callbacks_t *cbs; /** callbacks into higher DBUS level (dbus.c) */
    dbus_intf_t *drvintf;
    void *usbosl_info;
    uint32 rdlram_base_addr;
    uint32 rdlram_size;
} usb_info_t;

/*
 * Callbacks common to all USB
 */
static void dbus_usb_disconnect(void *handle);
static void dbus_usb_send_irb_timeout(void *handle, dbus_irb_tx_t *txirb);
static void dbus_usb_send_irb_complete(void *handle, dbus_irb_tx_t *txirb,
                                       int status);
static void dbus_usb_recv_irb_complete(void *handle, dbus_irb_rx_t *rxirb,
                                       int status);
static void dbus_usb_errhandler(void *handle, int err);
static void dbus_usb_ctl_complete(void *handle, int type, int status);
static void dbus_usb_state_change(void *handle, int state);
static struct dbus_irb *dbus_usb_getirb(void *handle, bool send);
static void dbus_usb_rxerr_indicate(void *handle, bool on);
#if !defined(BCM_REQUEST_FW)
static int dbus_usb_resetcfg(usb_info_t *usbinfo);
#endif
static int dbus_usb_iovar_op(void *bus, const char *name, void *params,
                             int plen, void *arg, int len, bool set);
static int dbus_iovar_process(usb_info_t *usbinfo, const char *name,
                              void *params, int plen, void *arg, int len,
                              bool set);
static int dbus_usb_doiovar(usb_info_t *bus, const bcm_iovar_t *vi,
                            uint32 actionid, const char *name, void *params,
                            int plen, void *arg, int len, int val_size);
static int dhdusb_downloadvars(usb_info_t *bus, void *arg, int len);

static int dbus_usb_dl_writeimage(usb_info_t *usbinfo, uint8 *fw, int fwlen);
static int dbus_usb_dlstart(void *bus, uint8 *fw, int len);
static int dbus_usb_dlneeded(void *bus);
static int dbus_usb_dlrun(void *bus);
static int dbus_usb_rdl_dwnld_state(usb_info_t *usbinfo);

/* OS specific */
extern bool dbus_usbos_dl_cmd(void *info, uint8 cmd, void *buffer, int buflen);
extern int dbus_usbos_wait(void *info, uint16 ms);
extern int dbus_write_membytes(usb_info_t *usbinfo, bool set, uint32 address,
                               uint8 *data, uint size);
extern bool dbus_usbos_dl_send_bulk(void *info, void *buffer, int len);
extern int dbus_usbos_loopback_tx(void *usbos_info_ptr, int cnt, int size);

/**
 * These functions are called by the lower DBUS level (dbus_usb_os.c) to notify
 * this DBUS level (dbus_usb.c) of an event.
 */
static dbus_intf_callbacks_t dbus_usb_intf_cbs = {dbus_usb_send_irb_timeout,
                                                  dbus_usb_send_irb_complete,
                                                  dbus_usb_recv_irb_complete,
                                                  dbus_usb_errhandler,
                                                  dbus_usb_ctl_complete,
                                                  dbus_usb_state_change,
                                                  NULL, /* isr */
                                                  NULL, /* dpc */
                                                  NULL, /* watchdog */
                                                  NULL, /* dbus_if_pktget */
                                                  NULL, /* dbus_if_pktfree */
                                                  dbus_usb_getirb,
                                                  dbus_usb_rxerr_indicate};

/* IOVar table */
enum {
    IOV_SET_DOWNLOAD_STATE = 1,
    IOV_DBUS_MSGLEVEL,
    IOV_MEMBYTES,
    IOV_VARS,
    IOV_LOOPBACK_TX
};

const bcm_iovar_t dhdusb_iovars[] = {
    {"vars", IOV_VARS, 0, IOVT_BUFFER, 0},
    {"dbus_msglevel", IOV_DBUS_MSGLEVEL, 0, IOVT_UINT32, 0},
    {"dwnldstate", IOV_SET_DOWNLOAD_STATE, 0, IOVT_BOOL, 0},
    {"membytes", IOV_MEMBYTES, 0, IOVT_BUFFER, 2 * sizeof(int)},
    {"usb_lb_txfer", IOV_LOOPBACK_TX, 0, IOVT_BUFFER, 2 * sizeof(int)},
    {NULL, 0, 0, 0, 0}};

/*
 * Need global for probe() and disconnect() since
 * attach() is not called at probe and detach()
 * can be called inside disconnect()
 */
static probe_cb_t probe_cb = NULL;
static disconnect_cb_t disconnect_cb = NULL;
static void *probe_arg = NULL;
static void *disc_arg = NULL;
static dbus_intf_t *g_dbusintf = NULL;
static dbus_intf_t
    dbus_usb_intf; /** functions called by higher layer DBUS into lower layer */

/*
 * dbus_intf_t common to all USB
 * These functions override dbus_usb_<os>.c.
 */
static void *dbus_usb_attach(dbus_pub_t *pub, void *cbarg,
                             dbus_intf_callbacks_t *cbs);
static void dbus_usb_detach(dbus_pub_t *pub, void *info);
static void *dbus_usb_probe(void *arg, const char *desc, uint32 bustype,
                            uint16 bus_no, uint16 slot, uint32 hdrlen);

/* functions */

/**
 * As part of DBUS initialization/registration, the higher level DBUS (dbus.c)
 * needs to know what lower level DBUS functions to call (in both dbus_usb.c and
 * dbus_usb_os.c).
 */
static void *dbus_usb_probe(void *arg, const char *desc, uint32 bustype,
                            uint16 bus_no, uint16 slot, uint32 hdrlen)
{
    DBUSTRACE(("%s(): \n", __FUNCTION__));
    if (probe_cb) {
        if (g_dbusintf != NULL) {
            /* First, initialize all lower-level functions as default
             * so that dbus.c simply calls directly to dbus_usb_os.c.
             */
            bcopy(g_dbusintf, &dbus_usb_intf, sizeof(dbus_intf_t));

            /* Second, selectively override functions we need, if any. */
            dbus_usb_intf.attach = dbus_usb_attach;
            dbus_usb_intf.detach = dbus_usb_detach;
            dbus_usb_intf.iovar_op = dbus_usb_iovar_op;
            dbus_usb_intf.dlstart = dbus_usb_dlstart;
            dbus_usb_intf.dlneeded = dbus_usb_dlneeded;
            dbus_usb_intf.dlrun = dbus_usb_dlrun;
        }

        disc_arg =
            probe_cb(probe_arg, "DBUS USB", USB_BUS, bus_no, slot, hdrlen);
        return disc_arg;
    }

    return NULL;
}

/**
 * On return, *intf contains this or lower-level DBUS functions to be called by
 * higher level (dbus.c)
 */
int dbus_bus_register(int vid, int pid, probe_cb_t prcb, disconnect_cb_t discb,
                      void *prarg, dbus_intf_t **intf, void *param1,
                      void *param2)
{
    int err;

    DBUSTRACE(("%s(): \n", __FUNCTION__));
    probe_cb = prcb;
    disconnect_cb = discb;
    probe_arg = prarg;

    *intf = &dbus_usb_intf;

    err = dbus_bus_osl_register(vid, pid, dbus_usb_probe, dbus_usb_disconnect,
                                NULL, &g_dbusintf, param1, param2);

    ASSERT(g_dbusintf);
    return err;
}

int dbus_bus_deregister()
{
    DBUSTRACE(("%s(): \n", __FUNCTION__));
    return dbus_bus_osl_deregister();
}

/** initialization consists of registration followed by 'attach'. */
void *dbus_usb_attach(dbus_pub_t *pub, void *cbarg, dbus_intf_callbacks_t *cbs)
{
    usb_info_t *usb_info;

    DBUSTRACE(("%s(): \n", __FUNCTION__));

    if ((g_dbusintf == NULL) || (g_dbusintf->attach == NULL)) {
        return NULL;
    }

    /* Sanity check for BUS_INFO() */
    ASSERT(OFFSETOF(usb_info_t, pub) == 0);

    usb_info = MALLOC(pub->osh, sizeof(usb_info_t));
    if (usb_info == NULL) {
        return NULL;
    }

    bzero(usb_info, sizeof(usb_info_t));

    usb_info->pub = pub;
    usb_info->cbarg = cbarg;
    usb_info->cbs = cbs;

    usb_info->usbosl_info =
        (dbus_pub_t *)g_dbusintf->attach(pub, usb_info, &dbus_usb_intf_cbs);
    if (usb_info->usbosl_info == NULL) {
        MFREE(pub->osh, usb_info, sizeof(usb_info_t));
        return NULL;
    }

    /* Save USB OS-specific driver entry points */
    usb_info->drvintf = g_dbusintf;

    pub->bus = usb_info;
#if !defined(BCM_REQUEST_FW)
    if (!dbus_usb_resetcfg(usb_info)) {
        usb_info->pub->busstate = DBUS_STATE_DL_DONE;
    }
#endif
    /* Return Lower layer info */
    return (void *)usb_info->usbosl_info;
}

void dbus_usb_detach(dbus_pub_t *pub, void *info)
{
    usb_info_t *usb_info = (usb_info_t *)pub->bus;
    osl_t *osh = pub->osh;

    if (usb_info == NULL) {
        return;
    }

    if (usb_info->drvintf && usb_info->drvintf->detach) {
        usb_info->drvintf->detach(pub, usb_info->usbosl_info);
    }

    MFREE(osh, usb_info, sizeof(usb_info_t));
}

void dbus_usb_disconnect(void *handle)
{
    DBUSTRACE(("%s(): \n", __FUNCTION__));
    if (disconnect_cb) {
        disconnect_cb(disc_arg);
    }
}

/**
 * When the lower DBUS level (dbus_usb_os.c) signals this event, the higher DBUS
 * level has to be notified.
 */
static void dbus_usb_send_irb_timeout(void *handle, dbus_irb_tx_t *txirb)
{
    usb_info_t *usb_info = (usb_info_t *)handle;

    DBUSTRACE(("%s\n", __FUNCTION__));

    if (usb_info == NULL) {
        return;
    }

    if (usb_info->cbs && usb_info->cbs->send_irb_timeout) {
        usb_info->cbs->send_irb_timeout(usb_info->cbarg, txirb);
    }
}

/**
 * When the lower DBUS level (dbus_usb_os.c) signals this event, the higher DBUS
 * level has to be notified.
 */
static void dbus_usb_send_irb_complete(void *handle, dbus_irb_tx_t *txirb,
                                       int status)
{
    usb_info_t *usb_info = (usb_info_t *)handle;

    if (usb_info == NULL) {
        return;
    }

    if (usb_info->cbs && usb_info->cbs->send_irb_complete) {
        usb_info->cbs->send_irb_complete(usb_info->cbarg, txirb, status);
    }
}

/**
 * When the lower DBUS level (dbus_usb_os.c) signals this event, the higher DBUS
 * level has to be notified.
 */
static void dbus_usb_recv_irb_complete(void *handle, dbus_irb_rx_t *rxirb,
                                       int status)
{
    usb_info_t *usb_info = (usb_info_t *)handle;

    if (usb_info == NULL) {
        return;
    }

    if (usb_info->cbs && usb_info->cbs->recv_irb_complete) {
        usb_info->cbs->recv_irb_complete(usb_info->cbarg, rxirb, status);
    }
}

/** Lower DBUS level (dbus_usb_os.c) requests a free IRB. Pass this on to the
 * higher DBUS level. */
static struct dbus_irb *dbus_usb_getirb(void *handle, bool send)
{
    usb_info_t *usb_info = (usb_info_t *)handle;

    if (usb_info == NULL) {
        return NULL;
    }

    if (usb_info->cbs && usb_info->cbs->getirb) {
        return usb_info->cbs->getirb(usb_info->cbarg, send);
    }

    return NULL;
}

/**
 * When the lower DBUS level (dbus_usb_os.c) signals this event, the higher DBUS
 * level has to be notified.
 */
static void dbus_usb_rxerr_indicate(void *handle, bool on)
{
    usb_info_t *usb_info = (usb_info_t *)handle;

    if (usb_info == NULL) {
        return;
    }

    if (usb_info->cbs && usb_info->cbs->rxerr_indicate) {
        usb_info->cbs->rxerr_indicate(usb_info->cbarg, on);
    }
}

/**
 * When the lower DBUS level (dbus_usb_os.c) signals this event, the higher DBUS
 * level has to be notified.
 */
static void dbus_usb_errhandler(void *handle, int err)
{
    usb_info_t *usb_info = (usb_info_t *)handle;

    if (usb_info == NULL) {
        return;
    }

    if (usb_info->cbs && usb_info->cbs->errhandler) {
        usb_info->cbs->errhandler(usb_info->cbarg, err);
    }
}

/**
 * When the lower DBUS level (dbus_usb_os.c) signals this event, the higher DBUS
 * level has to be notified.
 */
static void dbus_usb_ctl_complete(void *handle, int type, int status)
{
    usb_info_t *usb_info = (usb_info_t *)handle;

    DBUSTRACE(("%s\n", __FUNCTION__));

    if (usb_info == NULL) {
        DBUSERR(("%s: usb_info is NULL\n", __FUNCTION__));
        return;
    }

    if (usb_info->cbs && usb_info->cbs->ctl_complete) {
        usb_info->cbs->ctl_complete(usb_info->cbarg, type, status);
    }
}

/**
 * When the lower DBUS level (dbus_usb_os.c) signals this event, the higher DBUS
 * level has to be notified.
 */
static void dbus_usb_state_change(void *handle, int state)
{
    usb_info_t *usb_info = (usb_info_t *)handle;

    if (usb_info == NULL) {
        return;
    }

    if (usb_info->cbs && usb_info->cbs->state_change) {
        usb_info->cbs->state_change(usb_info->cbarg, state);
    }
}

/** called by higher DBUS level (dbus.c) */
static int dbus_usb_iovar_op(void *bus, const char *name, void *params,
                             int plen, void *arg, int len, bool set)
{
    int err = DBUS_OK;

    err = dbus_iovar_process((usb_info_t *)bus, name, params, plen, arg, len,
                             set);
    return err;
}

/** process iovar request from higher DBUS level */
static int dbus_iovar_process(usb_info_t *usbinfo, const char *name,
                              void *params, int plen, void *arg, int len,
                              bool set)
{
    const bcm_iovar_t *vi = NULL;
    int bcmerror = 0;
    int val_size;
    uint32 actionid;

    DBUSTRACE(("%s: Enter\n", __FUNCTION__));

    ASSERT(name);
    ASSERT(len >= 0);

    /* Get MUST have return space */
    ASSERT(set || (arg && len));

    /* Set does NOT take qualifiers */
    ASSERT(!set || (!params && !plen));

    /* Look up var locally; if not found pass to host driver */
    if ((vi = bcm_iovar_lookup(dhdusb_iovars, name)) == NULL) {
        /* Not Supported */
        bcmerror = BCME_UNSUPPORTED;
        DBUSTRACE(("%s: IOVAR %s is not supported\n", name, __FUNCTION__));
        goto exit;
    }

    DBUSTRACE(("%s: %s %s, len %d plen %d\n", __FUNCTION__, name,
               (set ? "set" : "get"), len, plen));

    /* set up 'params' pointer in case this is a set command so that
     * the convenience int and bool code can be common to set and get
     */
    if (params == NULL) {
        params = arg;
        plen = len;
    }

    if (vi->type == IOVT_VOID) {
        val_size = 0;
    } else if (vi->type == IOVT_BUFFER) {
        val_size = len;
    } else {
        /* all other types are integer sized */
        val_size = sizeof(int);
    }

    actionid = set ? IOV_SVAL(vi->varid) : IOV_GVAL(vi->varid);
    bcmerror = dbus_usb_doiovar(usbinfo, vi, actionid, name, params, plen, arg,
                                len, val_size);

exit:
    return bcmerror;
} /* dbus_iovar_process */

static int dbus_usb_doiovar(usb_info_t *bus, const bcm_iovar_t *vi,
                            uint32 actionid, const char *name, void *params,
                            int plen, void *arg, int len, int val_size)
{
    int bcmerror = 0;
    int32 int_val = 0;
    int32 int_val2 = 0;
    bool bool_val = 0;

    DBUSTRACE(("%s: Enter, action %d name %s params %p plen %d arg %p len %d "
               "val_size %d\n",
               __FUNCTION__, actionid, name, params, plen, arg, len, val_size));

    if ((bcmerror = bcm_iovar_lencheck(vi, arg, len, IOV_ISSET(actionid))) !=
        0) {
        goto exit;
    }

    if (plen >= (int)sizeof(int_val)) {
        bcopy(params, &int_val, sizeof(int_val));
    }

    if (plen >= (int)sizeof(int_val) * 0x2) {
        bcopy((void *)((uintptr)params + sizeof(int_val)), &int_val2,
              sizeof(int_val2));
    }

    bool_val = (int_val != 0) ? TRUE : FALSE;

    switch (actionid) {
        case IOV_SVAL(IOV_MEMBYTES):
        case IOV_GVAL(IOV_MEMBYTES): {
            uint32 address;
            uint size, dsize;
            uint8 *data;

            bool set = (actionid == IOV_SVAL(IOV_MEMBYTES));

            ASSERT(plen >= 0x2 * sizeof(int));

            address = (uint32)int_val;
            BCM_REFERENCE(address);
            bcopy((char *)params + sizeof(int_val), &int_val, sizeof(int_val));
            size = (uint)int_val;

            /* Do some validation */
            dsize = set ? plen - (0x2 * sizeof(int)) : len;
            if (dsize < size) {
                DBUSTRACE((
                    "%s: error on %s membytes, addr 0x%08x size %d dsize %d\n",
                    __FUNCTION__, (set ? "set" : "get"), address, size, dsize));
                bcmerror = BCME_BADARG;
                break;
            }
            DBUSTRACE(("%s: Request to %s %d bytes at address 0x%08x\n",
                       __FUNCTION__, (set ? "write" : "read"), size, address));

            /* Generate the actual data pointer */
            data = set ? (uint8 *)params + 0x2 * sizeof(int) : (uint8 *)arg;

            /* Call to do the transfer */
            bcmerror =
                dbus_usb_dl_writeimage(BUS_INFO(bus, usb_info_t), data, size);
            break;
        }
        case IOV_SVAL(IOV_SET_DOWNLOAD_STATE):
            if (bool_val == TRUE) {
                bcmerror = dbus_usb_dlneeded(bus);
                dbus_usb_rdl_dwnld_state(BUS_INFO(bus, usb_info_t));
            } else {
                usb_info_t *usbinfo = BUS_INFO(bus, usb_info_t);
                bcmerror = dbus_usb_dlrun(bus);
                usbinfo->pub->busstate = DBUS_STATE_DL_DONE;
            }
            break;
        case IOV_SVAL(IOV_VARS):
            bcmerror = dhdusb_downloadvars(BUS_INFO(bus, usb_info_t), arg, len);
            break;
        case IOV_GVAL(IOV_DBUS_MSGLEVEL):
            int_val = (int32)dbus_msglevel;
            bcopy(&int_val, arg, val_size);
            break;
        case IOV_SVAL(IOV_DBUS_MSGLEVEL):
            dbus_msglevel = int_val;
            break;

#ifdef DBUS_USB_LOOPBACK
        case IOV_SVAL(IOV_LOOPBACK_TX):
            bcmerror = dbus_usbos_loopback_tx(BUS_INFO(bus, usb_info_t),
                                              int_val, int_val2);
            break;
#endif
        default:
            bcmerror = BCME_UNSUPPORTED;
            break;
    }

exit:
    return bcmerror;
} /* dbus_usb_doiovar */

/** higher DBUS level (dbus.c) wants to set NVRAM variables in dongle */
static int dhdusb_downloadvars(usb_info_t *bus, void *arg, int len)
{
    int bcmerror = 0;
    uint32 varsize;
    uint32 varaddr;
    uint32 varsizew;

    if (!len) {
        bcmerror = BCME_BUFTOOSHORT;
        goto err;
    }

    /* RAM size is not set. Set it at dbus_usb_dlneeded */
    if (!bus->rdlram_size) {
        bcmerror = BCME_ERROR;
    }

    /* Even if there are no vars are to be written, we still need to set the
     * ramsize. */
    varsize = len ? ROUNDUP(len, 0x4) : 0;
    varaddr = (bus->rdlram_size - 0x4) - varsize;

    /* Write the vars list */
    DBUSTRACE(("WriteVars: @%x varsize=%d\n", varaddr, varsize));
    bcmerror =
        dbus_write_membytes(bus->usbosl_info, TRUE,
                            (varaddr + bus->rdlram_base_addr), arg, varsize);

    /* adjust to the user specified RAM */
    DBUSTRACE(("Usable memory size: %d\n", bus->rdlram_size));
    DBUSTRACE(("Vars are at %d, orig varsize is %d\n", varaddr, varsize));

    varsize = ((bus->rdlram_size - 0x4) - varaddr);

    /*
     * Determine the length token:
     * Varsize, converted to words, in lower 16-bits, checksum in upper 16-bits.
     */
    if (bcmerror) {
        varsizew = 0;
    } else {
        varsizew = varsize / 0x4;
        varsizew = (~varsizew << 0x10) | (varsizew & 0x0000FFFF);
        varsizew = htol32(varsizew);
    }

    DBUSTRACE(("New varsize is %d, length token=0x%08x\n", varsize, varsizew));

    /* Write the length token to the last word */
    bcmerror =
        dbus_write_membytes(bus->usbosl_info, TRUE,
                            ((bus->rdlram_size - 0x4) + bus->rdlram_base_addr),
                            (uint8 *)&varsizew, 0x4);
err:
    return bcmerror;
} /* dbus_usb_doiovar */

#if !defined(BCM_REQUEST_FW)
/**
 * After downloading firmware into dongle and starting it, we need to know if
 * the firmware is indeed up and running.
 */
static int dbus_usb_resetcfg(usb_info_t *usbinfo)
{
    void *osinfo;
    bootrom_id_t id;
    uint16 waittime = 0;

    uint32 starttime = 0;
    uint32 endtime = 0;

    DBUSTRACE(("%s\n", __FUNCTION__));

    if (usbinfo == NULL) {
        return DBUS_ERR;
    }

    osinfo = usbinfo->usbosl_info;
    ASSERT(osinfo);

    /* Give dongle chance to boot */
    dbus_usbos_wait(osinfo, USB_SFLASH_DLIMAGE_SPINWAIT);
    waittime = USB_SFLASH_DLIMAGE_SPINWAIT;
    while (waittime < USB_DLIMAGE_RETRY_TIMEOUT) {
        starttime = OSL_SYSUPTIME();

        id.chip = 0xDEAD; /* Get the ID */
        dbus_usbos_dl_cmd(osinfo, DL_GETVER, &id, sizeof(bootrom_id_t));
        id.chip = ltoh32(id.chip);

        endtime = OSL_SYSUPTIME();
        waittime += (endtime - starttime);

        if (id.chip == POSTBOOT_ID) {
            break;
        }
    }

    if (id.chip == POSTBOOT_ID) {
        DBUSERR(("%s: download done. Bootup time = %d ms postboot chip "
                 "0x%x/rev 0x%x\n",
                 __FUNCTION__, waittime, id.chip, id.chiprev));

        dbus_usbos_dl_cmd(osinfo, DL_RESETCFG, &id, sizeof(bootrom_id_t));

        dbus_usbos_wait(osinfo, USB_RESETCFG_SPINWAIT);
        return DBUS_OK;
    } else {
        DBUSERR(("%s: Cannot talk to Dongle. Wait time = %d ms. Firmware is "
                 "not UP \n",
                 __FUNCTION__, waittime));
        return DBUS_ERR;
    }

    return DBUS_OK;
}
#endif

/** before firmware download, the dongle has to be prepared to receive the fw
 * image */
static int dbus_usb_rdl_dwnld_state(usb_info_t *usbinfo)
{
    void *osinfo = usbinfo->usbosl_info;
    rdl_state_t state;
    int err = DBUS_OK;

    /* 1) Prepare USB boot loader for runtime image */
    dbus_usbos_dl_cmd(osinfo, DL_START, &state, sizeof(rdl_state_t));

    state.state = ltoh32(state.state);
    state.bytes = ltoh32(state.bytes);

    /* 2) Check we are in the Waiting state */
    if (state.state != DL_WAITING) {
        DBUSERR(("%s: Failed to DL_START\n", __FUNCTION__));
        err = DBUS_ERR;
        goto fail;
    }

fail:
    return err;
}

/**
 * Dongle contains bootcode in ROM but firmware is (partially) contained in
 * dongle RAM. Therefore, firmware has to be downloaded into dongle RAM.
 */
static int dbus_usb_dl_writeimage(usb_info_t *usbinfo, uint8 *fw, int fwlen)
{
    osl_t *osh = usbinfo->pub->osh;
    void *osinfo = usbinfo->usbosl_info;
    unsigned int sendlen, sent, dllen;
    char *bulkchunk = NULL, *dlpos;
    rdl_state_t state;
    int err = DBUS_OK;
    bootrom_id_t id;
    uint16 wait, wait_time;
    uint32 dl_trunk_size = RDL_CHUNK;

    if (BCM4350_CHIP(usbinfo->pub->attrib.devid)) {
        dl_trunk_size = RDL_CHUNK_MAX;
    }

    while (!bulkchunk) {
        bulkchunk = MALLOC(osh, dl_trunk_size);
        if (dl_trunk_size == RDL_CHUNK) {
            break;
        }
        if (!bulkchunk) {
            dl_trunk_size /= 0x2;
            if (dl_trunk_size < RDL_CHUNK) {
                dl_trunk_size = RDL_CHUNK;
            }
        }
    }

    if (bulkchunk == NULL) {
        err = DBUS_ERR;
        goto fail;
    }

    sent = 0;
    dlpos = fw;
    dllen = fwlen;

    /* Get chip id and rev */
    id.chip = usbinfo->pub->attrib.devid;
    id.chiprev = usbinfo->pub->attrib.chiprev;

    DBUSTRACE(("enter %s: fwlen=%d\n", __FUNCTION__, fwlen));

    dbus_usbos_dl_cmd(osinfo, DL_GETSTATE, &state, sizeof(rdl_state_t));

    /* 3) Load the image */
    while ((sent < dllen)) {
        /* Wait until the usb device reports it received all the bytes we sent
         */

        if (sent < dllen) {
            if ((dllen - sent) < dl_trunk_size) {
                sendlen = dllen - sent;
            } else {
                sendlen = dl_trunk_size;
            }

            /* simply avoid having to send a ZLP by ensuring we never have an
             * even multiple of 64
             */
            if (!(sendlen % 0x40)) {
                sendlen -= 0x4;
            }

            /* send data */
            memcpy(bulkchunk, dlpos, sendlen);
            if (!dbus_usbos_dl_send_bulk(osinfo, bulkchunk, sendlen)) {
                err = DBUS_ERR;
                goto fail;
            }

            dlpos += sendlen;
            sent += sendlen;
            DBUSTRACE(("%s: sendlen %d\n", __FUNCTION__, sendlen));
        }

        wait = 0;
        wait_time = USB_SFLASH_DLIMAGE_SPINWAIT;
        while (!dbus_usbos_dl_cmd(osinfo, DL_GETSTATE, &state,
                                  sizeof(rdl_state_t))) {
            if ((id.chip == 0xA8E4) && (id.chiprev == 0)) {
                DBUSERR(
                    ("%s: 43236a0 SFlash delay, waiting for dongle crc check "
                     "completion!!!\n",
                     __FUNCTION__));
                dbus_usbos_wait(osinfo, wait_time);
                wait += wait_time;
                if (wait >= USB_SFLASH_DLIMAGE_LIMIT) {
                    DBUSERR(("%s: DL_GETSTATE Failed xxxx\n", __FUNCTION__));
                    err = DBUS_ERR;
                    goto fail;
                    break;
                }
            } else {
                DBUSERR(("%s: DL_GETSTATE Failed xxxx\n", __FUNCTION__));
                err = DBUS_ERR;
                goto fail;
            }
        }

        state.state = ltoh32(state.state);
        state.bytes = ltoh32(state.bytes);

        /* restart if an error is reported */
        if ((state.state == DL_BAD_HDR) || (state.state == DL_BAD_CRC)) {
            DBUSERR(("%s: Bad Hdr or Bad CRC\n", __FUNCTION__));
            err = DBUS_ERR;
            goto fail;
        }
    }
fail:
    if (bulkchunk) {
        MFREE(osh, bulkchunk, dl_trunk_size);
    }

    return err;
} /* dbus_usb_dl_writeimage */

/** Higher level DBUS layer (dbus.c) requests this layer to download image into
 * dongle */
static int dbus_usb_dlstart(void *bus, uint8 *fw, int len)
{
    usb_info_t *usbinfo = BUS_INFO(bus, usb_info_t);
    int err;

    DBUSTRACE(("%s\n", __FUNCTION__));

    if (usbinfo == NULL) {
        return DBUS_ERR;
    }

    if (USB_DEV_ISBAD(usbinfo)) {
        return DBUS_ERR;
    }

    err = dbus_usb_rdl_dwnld_state(usbinfo);
    if (DBUS_OK == err) {
        err = dbus_usb_dl_writeimage(usbinfo, fw, len);
        if (err == DBUS_OK) {
            usbinfo->pub->busstate = DBUS_STATE_DL_DONE;
        } else {
            usbinfo->pub->busstate = DBUS_STATE_DL_PENDING;
        }
    } else {
        usbinfo->pub->busstate = DBUS_STATE_DL_PENDING;
    }
    return err;
}

static bool dbus_usb_update_chipinfo(usb_info_t *usbinfo, uint32 chip)
{
    bool retval = TRUE;
    /* based on the CHIP Id, store the ram size which is needed for NVRAM
     * download. */
    switch (chip) {
        case 0x4319:
            usbinfo->rdlram_size = RDL_RAM_SIZE_4319;
            usbinfo->rdlram_base_addr = RDL_RAM_BASE_4319;
            break;

        case 0x4329:
            usbinfo->rdlram_size = RDL_RAM_SIZE_4329;
            usbinfo->rdlram_base_addr = RDL_RAM_BASE_4329;
            break;

        case 0xA8E2:
        case 0xA8E3:
        case 0xA8E4:
            usbinfo->rdlram_size = RDL_RAM_SIZE_43236;
            usbinfo->rdlram_base_addr = RDL_RAM_BASE_43236;
            break;

        case 0x4328:
            usbinfo->rdlram_size = RDL_RAM_SIZE_4328;
            usbinfo->rdlram_base_addr = RDL_RAM_BASE_4328;
            break;

        case 0x4322:
            usbinfo->rdlram_size = RDL_RAM_SIZE_4322;
            usbinfo->rdlram_base_addr = RDL_RAM_BASE_4322;
            break;

        case 0x4360:
        case 0xAA06:
            usbinfo->rdlram_size = RDL_RAM_SIZE_4360;
            usbinfo->rdlram_base_addr = RDL_RAM_BASE_4360;
            break;

        case 0xA8EA:
        case 0xA8EB:
            usbinfo->rdlram_size = RDL_RAM_SIZE_43242;
            usbinfo->rdlram_base_addr = RDL_RAM_BASE_43242;
            break;

        case 0xA887:
            usbinfo->rdlram_size = RDL_RAM_SIZE_43143;
            usbinfo->rdlram_base_addr = RDL_RAM_BASE_43143;
            break;

        case 0x4350:
        case 0xAA24:
        case 0xAA26:
        case 0xAA31:
            usbinfo->rdlram_size = RDL_RAM_SIZE_4350;
            usbinfo->rdlram_base_addr = RDL_RAM_BASE_4350;
            break;

        case POSTBOOT_ID:
            break;

        default:
            DBUSERR(
                ("%s: Chip 0x%x Ram size is not known\n", __FUNCTION__, chip));
            retval = FALSE;
            break;
    }

    return retval;
} /* dbus_usb_update_chipinfo */

/** higher DBUS level (dbus.c) wants to know if firmware download is required.
 */
static int dbus_usb_dlneeded(void *bus)
{
    usb_info_t *usbinfo = BUS_INFO(bus, usb_info_t);
    void *osinfo;
    bootrom_id_t id;
    int dl_needed = 1;

    DBUSTRACE(("%s\n", __FUNCTION__));

    if (usbinfo == NULL) {
        return DBUS_ERR;
    }

    osinfo = usbinfo->usbosl_info;
    ASSERT(osinfo);

    /* Check if firmware downloaded already by querying runtime ID */
    id.chip = 0xDEAD;
    dbus_usbos_dl_cmd(osinfo, DL_GETVER, &id, sizeof(bootrom_id_t));

    id.chip = ltoh32(id.chip);
    id.chiprev = ltoh32(id.chiprev);

    if (FALSE == dbus_usb_update_chipinfo(usbinfo, id.chip)) {
        dl_needed = DBUS_ERR;
        goto exit;
    }

    DBUSERR(("%s: chip 0x%x rev 0x%x\n", __FUNCTION__, id.chip, id.chiprev));
    if (id.chip == POSTBOOT_ID) {
        /* This code is  needed to support two enumerations on USB1.1 scenario
         */
        DBUSERR(("%s: Firmware already downloaded\n", __FUNCTION__));

        dbus_usbos_dl_cmd(osinfo, DL_RESETCFG, &id, sizeof(bootrom_id_t));
        dl_needed = DBUS_OK;
        if (usbinfo->pub->busstate == DBUS_STATE_DL_PENDING) {
            usbinfo->pub->busstate = DBUS_STATE_DL_DONE;
        }
    } else {
        usbinfo->pub->attrib.devid = id.chip;
        usbinfo->pub->attrib.chiprev = id.chiprev;
    }

exit:
    return dl_needed;
}

/** After issuing firmware download, higher DBUS level (dbus.c) wants to start
 * the firmware. */
static int dbus_usb_dlrun(void *bus)
{
    usb_info_t *usbinfo = BUS_INFO(bus, usb_info_t);
    void *osinfo;
    rdl_state_t state;
    int err = DBUS_OK;

    DBUSTRACE(("%s\n", __FUNCTION__));

    if (usbinfo == NULL) {
        return DBUS_ERR;
    }

    if (USB_DEV_ISBAD(usbinfo)) {
        return DBUS_ERR;
    }

    osinfo = usbinfo->usbosl_info;
    ASSERT(osinfo);

    /* Check we are runnable */
    dbus_usbos_dl_cmd(osinfo, DL_GETSTATE, &state, sizeof(rdl_state_t));

    state.state = ltoh32(state.state);
    state.bytes = ltoh32(state.bytes);

    /* Start the image */
    if (state.state == DL_RUNNABLE) {
        DBUSTRACE(("%s: Issue DL_GO\n", __FUNCTION__));
        dbus_usbos_dl_cmd(osinfo, DL_GO, &state, sizeof(rdl_state_t));

        if (usbinfo->pub->attrib.devid == TEST_CHIP) {
            dbus_usbos_wait(osinfo, USB_DLGO_SPINWAIT);
        }
    } else {
        DBUSERR(("%s: Dongle not runnable\n", __FUNCTION__));
        err = DBUS_ERR;
    }

    return err;
}

/**
 * As preparation for firmware download, higher DBUS level (dbus.c) requests the
 * firmware image to be used for the type of dongle detected. Directly called by
 * dbus.c (so not via a callback construction)
 */
void dbus_bus_fw_get(void *bus, uint8 **fw, int *fwlen, int *decomp)
{
    usb_info_t *usbinfo = BUS_INFO(bus, usb_info_t);
    unsigned int devid;
    unsigned int crev;

    devid = usbinfo->pub->attrib.devid;
    crev = usbinfo->pub->attrib.chiprev;

    *fw = NULL;
    *fwlen = 0;

    switch (devid) {
        case BCM43236_CHIP_ID:
        case BCM43235_CHIP_ID:
        case BCM43234_CHIP_ID:
        case BCM43238_CHIP_ID: {
            if (crev == 0x3 || crev == 0x2 || crev == 1) {
#ifdef EMBED_IMAGE_43236b
                *fw = (uint8 *)dlarray_43236b;
                *fwlen = sizeof(dlarray_43236b);

#endif
            }
            break;
        }
        case BCM4360_CHIP_ID:
        case BCM4352_CHIP_ID:
        case BCM43526_CHIP_ID:
#ifdef EMBED_IMAGE_43526a
            if (crev <= 2) {
                *fw = (uint8 *)dlarray_43526a;
                *fwlen = sizeof(dlarray_43526a);
            }
#endif
#ifdef EMBED_IMAGE_43526b
            if (crev > 2) {
                *fw = (uint8 *)dlarray_43526b;
                *fwlen = sizeof(dlarray_43526b);
            }
#endif
            break;

        case BCM43242_CHIP_ID:
#ifdef EMBED_IMAGE_43242a0
            *fw = (uint8 *)dlarray_43242a0;
            *fwlen = sizeof(dlarray_43242a0);
#endif
            break;

        case BCM43143_CHIP_ID:
#ifdef EMBED_IMAGE_43143a0
            *fw = (uint8 *)dlarray_43143a0;
            *fwlen = sizeof(dlarray_43143a0);
#endif
#ifdef EMBED_IMAGE_43143b0
            *fw = (uint8 *)dlarray_43143b0;
            *fwlen = sizeof(dlarray_43143b0);
#endif
            break;

        case BCM4350_CHIP_ID:
        case BCM4354_CHIP_ID:
        case BCM43556_CHIP_ID:
        case BCM43558_CHIP_ID:
        case BCM43566_CHIP_ID:
        case BCM43568_CHIP_ID:
        case BCM43570_CHIP_ID:
        case BCM4358_CHIP_ID:
#ifdef EMBED_IMAGE_4350a0
            if (crev == 0) {
                *fw = (uint8 *)dlarray_4350a0;
                *fwlen = sizeof(dlarray_4350a0);
            }
#endif
#ifdef EMBED_IMAGE_4350b0
            if (crev == 1) {
                *fw = (uint8 *)dlarray_4350b0;
                *fwlen = sizeof(dlarray_4350b0);
            }
#endif
#ifdef EMBED_IMAGE_4350b1
            if (crev == 0x2) {
                *fw = (uint8 *)dlarray_4350b1;
                *fwlen = sizeof(dlarray_4350b1);
            }
#endif
#ifdef EMBED_IMAGE_43556b1
            if (crev == 0x2) {
                *fw = (uint8 *)dlarray_43556b1;
                *fwlen = sizeof(dlarray_43556b1);
            }
#endif
#ifdef EMBED_IMAGE_4350c0
            if (crev == 0x3) {
                *fw = (uint8 *)dlarray_4350c0;
                *fwlen = sizeof(dlarray_4350c0);
            }
#endif /* EMBED_IMAGE_4350c0 */
#ifdef EMBED_IMAGE_4350c1
            if (crev == 4) {
                *fw = (uint8 *)dlarray_4350c1;
                *fwlen = sizeof(dlarray_4350c1);
            }
#endif /* EMBED_IMAGE_4350c1 */
            break;
        case BCM43569_CHIP_ID:
#ifdef EMBED_IMAGE_43569a0
            if (crev == 0) {
                *fw = (uint8 *)dlarray_43569a0;
                *fwlen = sizeof(dlarray_43569a0);
            }
#endif /* EMBED_IMAGE_43569a0 */
            break;
        default:
#ifdef EMBED_IMAGE_GENERIC
            *fw = (uint8 *)dlarray;
            *fwlen = sizeof(dlarray);
#endif
            break;
    }
} /* dbus_bus_fw_get */
